
=======================================================================================================

                     Building PostgreSQL Client Libraries for DatabaseKit

=======================================================================================================

What is libpq?

	libpq is PostgreSQL's C client library. It is built from the PostgreSQL source as static libraries
	and linked directly into DatabaseKit.framework.

	libpq only supports encrypted connections (sslmode=require, verify-ca, verify-full) if it was
	configured with OpenSSL at build time. Without it, the connection parameters are rejected with
	"sslmode value ... invalid when SSL support is not compiled in".



What builds it?

	build_libpq.sh (in this folder) downloads the sources of OpenSSL and PostgreSQL, builds both as
	universal (arm64 + x86_64) static libraries, and puts the few files DatabaseKit needs into one folder:

		postgres/build/output/
			BUILD_INFO.txt                 what was built, with which versions, SDK, and minimum macOS
			openssl/include/openssl/*.h
			openssl/lib/                   libssl.a, libcrypto.a
			postgres/include/              libpq-fe.h, libpq-events.h, postgres_ext.h, pg_config.h
			postgres/lib/                  libpq.a, libpgport_shlib.a, libpgcommon_shlib.a

	DatabaseKit.xcodeproj references that output folder in place (see "Integrating into DatabaseKit").
	Nothing is copied anywhere else.

	postgres/build/ is listed in postgres/.gitignore, so none of it is in the repository. A fresh checkout
	must run the script before DatabaseKit will link. Downloads are kept in postgres/build/downloads,
	so later runs don't download again.

	A build takes about 2 minutes, almost all of it building OpenSSL twice (once per architecture). When
	nothing has changed, running it again does nothing (see below).



Current versions

	PostgreSQL 18.6   https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2
	OpenSSL    3.5.0  https://github.com/openssl/openssl/releases/download/openssl-3.5.0/openssl-3.5.0.tar.gz

	These, the architectures, the SDK, and the minimum macOS version are variables at the top of
	build_libpq.sh. To change a version, edit POSTGRES_URL or OPENSSL_URL and run the script again.

	MIN_MACOS is DatabaseKit's MACOSX_DEPLOYMENT_TARGET when run by Xcode, and 12.0 when run by hand.



Does this conflict with the OpenSSL the MySQL libraries use?

	No. They're completely separate: the MySQL client libraries are dynamic libraries that load their own
	OpenSSL 1.1.1 dylibs, while libpq here is linked statically with OpenSSL 3.5.0, and macOS's two-level
	namespace keeps each library's OpenSSL calls resolved to its own copy.



Does the libpq version decide which Postgres servers are supported?

	No. libpq is backward and forward compatible with servers: a libpq of any recent major version
	connects to older and newer servers, because the wire protocol (3.0) hasn't changed since version 7.4.
	It connects to Aurora/RDS and other compatible servers too.




-------------------------------------------------------------------------------------------------------
   What build_libpq.sh does
-------------------------------------------------------------------------------------------------------

It is meant to be run as the script of an Xcode target (like SQLParse's build_antlr4_runtime.sh), and
can also be run by hand with no arguments:

	cd ~/Development/DatabaseKit/postgres
	./build_libpq.sh

When run by Xcode it uses Xcode's MACOSX_DEPLOYMENT_TARGET as the minimum macOS and SDKROOT as the SDK.
Run by hand, those default to 12.0 and the active Xcode's SDK ($(xcrun --show-sdk-path)).
It needs network access (to download the sources the first time) and to write into postgres/build, so a
script phase running it needs ENABLE_USER_SCRIPT_SANDBOXING = NO.

The script's steps are numbered in the script itself. In order:

	- Fingerprint, and skip if unchanged. The fingerprint is a hash of the compiler version, the SDK path,
	  the two source URLs, the architectures, the minimum macOS, and the script file itself. It is saved in
	  postgres/build/output/_fingerprint, along with a hash of the five built libraries
	  (_libs_hash). If the fingerprint matches, all five libraries exist, and their hash matches, the script
	  prints "Skipping build" and exits in under a second. So any edit to the script (even a comment), a
	  change to a version or SDK, or a modified library, rebuilds. Delete postgres/build/output to force it.
	- Otherwise: clear CFLAGS, CPPFLAGS, LDFLAGS, LIBS, CC, CXX and SDKROOT from the environment (Xcode sets
	  many variables, and none of them should change the build), export MACOSX_DEPLOYMENT_TARGET, delete and
	  recreate postgres/build/stage and postgres/build/output, and download the two source archives into
	  postgres/build/downloads unless they are already there.
	- Build OpenSSL, then PostgreSQL, assemble the output, and verify (below). The output of the build
	  tools (configure, make, ranlib) is written to postgres/build/build.log instead of being printed,
	  because Xcode would show every line, including harmless tool warnings, as an issue. The script
	  prints only its own progress lines. If the build fails, it prints the last 40 lines of the log.
	- Write BUILD_INFO.txt, the fingerprint and the libraries' hash. They are written last, so an
	  interrupted or failed build leaves no fingerprint and the next run rebuilds.
	- Delete the unpacked sources and postgres/build/stage. Only postgres/build/downloads and
	  postgres/build/output remain.


1) OpenSSL, once per architecture

	OpenSSL builds one architecture at a time. For each of arm64 and x86_64 the script unpacks a fresh copy
	of the source into postgres/build/openssl-src-<arch>/ and runs (shown for arm64):

		./Configure darwin64-arm64-cc no-shared \
			--prefix=<stage>/openssl-arm64 --openssldir=<stage>/openssl-arm64/ssl --libdir=lib \
			-isysroot <SDK> -mmacosx-version-min=12.0
		make -j <ncpu> build_libs
		make install_dev

	(The target for x86_64 is darwin64-x86_64-cc.)

	no-shared builds only static libraries. build_libs builds libssl.a and libcrypto.a without the
	command line tools, and install_dev installs just the headers and libraries into the stage folder.
	--libdir=lib keeps the libraries in lib/ (not lib64/).

	The two architectures are then joined into universal libraries:

		lipo -create <stage>/openssl-arm64/lib/libssl.a <stage>/openssl-x86_64/lib/libssl.a \
			-output output/openssl/lib/libssl.a
		lipo -create ... libcrypto.a ... -output output/openssl/lib/libcrypto.a

	The headers are copied from the first architecture; the script warns if the two architectures'
	headers differ.


2) PostgreSQL, one pass for both architectures

	Postgres is configured and built into postgres/build/postgres-src/ (a fresh unpacked copy of the
	tarball each run, so the downloaded tarball and any other source are never modified):

		CFLAGS="-arch arm64 -arch x86_64 -isysroot <SDK> -mmacosx-version-min=12.0" \
		./configure --disable-debug --with-openssl --without-icu --without-readline --without-zlib \
			--prefix=<stage>/postgres \
			--with-includes=<output>/openssl/include \
			--with-libraries=<output>/openssl/lib

		make -C src/include
		make -C src/port libpgport_shlib.a
		make -C src/common libpgcommon_shlib.a
		make -C src/interfaces/libpq libpq.a

	--with-openssl enables SSL (configure stops with an error if it can't find OpenSSL).
	--with-includes / --with-libraries point at the OpenSSL just built in step 1, so libpq is
	configured against OpenSSL 3.5.0.

	--without-icu, --without-readline, --without-zlib: those are only used by the server and command
	line tools, not libpq, so they're turned off so that nothing else is needed on the build machine.

	Only the pieces libpq needs are built, not the whole tree:

		libpq.a             the client library. Built with "make -C src/interfaces/libpq libpq.a" rather than
		                    the whole "make -C src/interfaces", which also builds a shared libpq whose
		                    "must not call exit" self-check fails with the statically linked OpenSSL. The
		                    shared library isn't used.
		libpgport_shlib.a   portability functions libpq uses.
		libpgcommon_shlib.a shared code libpq uses (character encoding names, SCRAM, hashing via OpenSSL).

	The "_shlib" variants are the ones libpq.a is built to link with. In Postgres 18 the plain
	libpgcommon.a names pg_char_to_encoding() and pg_encoding_to_char() with a "_private" suffix, which
	libpq.a doesn't use, so linking against the plain library fails with
	"Undefined symbols: _pg_char_to_encoding, _pg_encoding_to_char".

	The embedded SQL libraries (libecpg, libecpg_compat, libpgtypes) are not built. Nothing in DatabaseKit
	uses them.

	The finished files are copied from the source tree into postgres/build/output/postgres/:
		src/interfaces/libpq/libpq.a, src/port/libpgport_shlib.a, src/common/libpgcommon_shlib.a
		src/interfaces/libpq/libpq-fe.h, libpq-events.h, src/include/postgres_ext.h, pg_config.h

	"make install" is not used.


3) Verification (the script stops with an ERROR if any of these fail)

	- For each architecture, no object file in any of the libraries requires a macOS version newer than
	  MIN_MACOS (otool -arch <arch> -l <lib> | grep minos).

	Nothing else is checked, because the build steps already fail if they go wrong: configure with
	--with-openssl stops if it can't find OpenSSL, and a missing architecture fails the compile or lipo.

	The same check can be run by hand:
		otool -arch arm64 -l postgres/build/output/postgres/lib/libpq.a | grep minos
	and to confirm SSL support and architectures:
		nm -arch arm64 postgres/build/output/postgres/lib/libpq.a | grep -c " U _SSL_"
		lipo -archs postgres/build/output/postgres/lib/libpq.a


What this does and doesn't establish about macOS 12

	The check above guarantees every object file was built for macOS 12.0 or earlier. In addition,
	the list of every external symbol the five libraries import from the system was reviewed once
	(nm -u across them): it contains only
	long-standing C library, pthread, socket, and CommonCrypto functions. The newest one found, going by
	the SDK headers' availability markings, is pwritev (macOS 11.0). Nothing needs newer than macOS 12.
	It has not been run on a macOS 12 machine.




-------------------------------------------------------------------------------------------------------
   Integrating into DatabaseKit
-------------------------------------------------------------------------------------------------------

DatabaseKit.xcodeproj uses the output folder where it is (postgres/build/output), in both Debug and Release:

	File references (in the project's "Frameworks" group, and the target's Frameworks build phase):
		postgres/build/output/postgres/lib/libpq.a
		postgres/build/output/postgres/lib/libpgport_shlib.a
		postgres/build/output/postgres/lib/libpgcommon_shlib.a
		postgres/build/output/openssl/lib/libssl.a
		postgres/build/output/openssl/lib/libcrypto.a

	HEADER_SEARCH_PATHS:
		$(PROJECT_DIR)/postgres/build/output/postgres/include

	LIBRARY_SEARCH_PATHS:
		$(PROJECT_DIR)/postgres/build/output/postgres/lib
		$(PROJECT_DIR)/postgres/build/output/openssl/lib
		(Xcode links the .a files above as -lpq, -lssl, etc., so it needs these to find them.)

	OTHER_LDFLAGS is just -stdlib=libc++.

The OpenSSL headers (openssl/include) are in the output only because libpq's build needs them, so the
project has no header search path for them.

Rebuilding with a new version just means running the script again. The file names don't change, so the
project doesn't need editing unless a version changes which files are produced.

To check that a built DatabaseKit has SSL:
	nm -arch arm64 DatabaseKit.framework/Versions/A/DatabaseKit | grep SSL_connect
	otool -L DatabaseKit.framework/Versions/A/DatabaseKit | grep -i ssl
		(should print nothing: OpenSSL is inside the binary, not a dynamic dependency)
